/*
 * SimulatorServer.hpp
 *
 *  Created on: 13 dec. 2012
 *      Author: Admin
 */

#ifndef SIMULATORSERVER_HPP_
#define SIMULATORSERVER_HPP_

#include <algorithm>
#include <cstdlib>

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#include <unistd.h>
#include <fcntl.h>

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>

#include <map>
#include <vector>

#include <sstream>

#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>

#include "Protocol.hpp"
#include "VirtualHardwareController.hpp"

#define PORT        	12345
#define BUFFERSIZE      4096

HANDLE 	messageQueueMutex = CreateMutex(NULL, FALSE, NULL),
        clientManagerMutex = CreateMutex(NULL, FALSE, NULL),
        handleMessageMutex = CreateMutex(NULL, FALSE, NULL);

void waitForMutex(HANDLE mutex)
{
	switch (WaitForSingleObject(
	            mutex,    // handle to mutex
	            INFINITE))  // no time-out interval
	{
		case WAIT_OBJECT_0:
			break;
		case WAIT_ABANDONED:
			throw std::exception();
	}
}

void releaseMutex(HANDLE mutex)
{
	if (! ReleaseMutex(mutex))
	{
		throw std::exception();
	}
}

struct ClientData
{
	ClientData(SOCKET aSocket, unsigned int aClientID) :
		socket(aSocket), clientID(aClientID)
	{ }

	SOCKET socket;
	unsigned int clientID;
};

struct ClientMessage
{
	ClientMessage(std::string aMessage) :
		message(aMessage),
		hasMessage(true)
	{ }

	ClientMessage() :
		hasMessage(false)
	{ }

	std::string message;
	bool hasMessage;
};

class MessageQueue
{
public:
	static MessageQueue& getInstance()
	{
		static MessageQueue mq;
		return mq;
	}

	void addClient(unsigned int clientID)
	{
		waitForMutex(messageQueueMutex);
		//Try and find this client in the queue
		std::map<unsigned int, std::vector < std::string > >::iterator it = messageQueue.find( clientID );
		if ( it == messageQueue.end() )
		{
			//This clientID did not exist in the queue
			//Create new queue for this client
			std::vector<std::string> clientQueue;
			//Insert pair
			messageQueue.insert( std::make_pair(clientID, clientQueue));
		}
		releaseMutex(messageQueueMutex);
	}

	void addMessage(unsigned int clientID, const std::string& message)
	{
		waitForMutex(messageQueueMutex);
		//Try and find this client in the queue
		std::map<unsigned int, std::vector < std::string > >::iterator it = messageQueue.find( clientID );
		if ( it == messageQueue.end() ) //This client does not exist
		{
			releaseMutex(messageQueueMutex);

			//Add new client
			addClient(clientID);
			//Retry adding the message
			addMessage(clientID, message);

			waitForMutex(messageQueueMutex);
		}
		//This clientID exists in the queue
		//Get the queue for this client
		std::vector<std::string>& clientQueue = it->second;
		//Add new message
		clientQueue.push_back(message);
		releaseMutex(messageQueueMutex);
	}

	void addMessage(const std::string& message)
	{
		waitForMutex(messageQueueMutex);
		if (messageQueue.size() > 0)
		//iterate through clients
			for (	std::map<unsigned int,
				std::vector < std::string > >::iterator it = messageQueue.begin();
				it != messageQueue.end();
				it++)
		{
			//Get the queue for this client
			std::vector<std::string>& clientQueue = it->second;
			//Add new message
			clientQueue.push_back(message);
		}
		releaseMutex(messageQueueMutex);
	}

	ClientMessage popNextMessage(unsigned int clientID)
	{
		waitForMutex(messageQueueMutex);
		//Try and find this client in the queue
		std::map<unsigned int, std::vector < std::string > >::iterator it = messageQueue.find( clientID );
		if ( it == messageQueue.end() ) //Client did not exists in the queue
		{
			releaseMutex(messageQueueMutex);
			return ClientMessage();
		}
		//This clientID exists in the queue
		//Get the queue for this client
		std::vector<std::string>& clientQueue = it->second;
		//Check if any messages are available
		if (clientQueue.size() < 1) //ClientQueue is empty
		{
			releaseMutex(messageQueueMutex);
			return ClientMessage();
		}
		//Get next message
		std::string message = *(clientQueue.begin());
		ClientMessage clientMessage(message);
		//Pop it from the queue
		clientQueue.erase(clientQueue.begin());

		releaseMutex(messageQueueMutex);
		//Return it as ClientMessage
		return clientMessage;
	}

private:
	std::map < unsigned int, std::vector < std::string > > messageQueue;

	MessageQueue() { }
	MessageQueue(const MessageQueue&);
};

class ClientManager
{
public:
	static ClientManager& getInstance()
	{
		static ClientManager cm;
		return cm;
	}

	void addClient(unsigned int clientID)
	{
		waitForMutex(clientManagerMutex);
		clientIDs.push_back(clientID);
		MessageQueue::getInstance().addClient(clientID);
		releaseMutex(clientManagerMutex);
	}

	void removeClient(unsigned int clientID)
	{
		waitForMutex(clientManagerMutex);
		//Find clientID
		std::vector<unsigned int>::iterator it = find(clientIDs.begin(), clientIDs.end(), clientID);
		if (it != clientIDs.end()) //Client found
			clientIDs.erase(it);

		releaseMutex(clientManagerMutex);
	}

	bool hasClient(unsigned int clientID)
	{
		waitForMutex(clientManagerMutex);
		std::vector<unsigned int>::iterator it = find(clientIDs.begin(), clientIDs.end(), clientID);
		bool result = !(it == clientIDs.end());
		releaseMutex(clientManagerMutex);
		return result;
	}

//	std::vector<unsigned int> getClients()
//	{
//		return clientIDs;
//	}

private:
	std::vector<unsigned int> clientIDs;
	ClientManager() { }
	ClientManager(const ClientManager&);
};

void handleMessage(std::string message)
{
	waitForMutex(handleMessageMutex);
	boost::char_separator<char> sep(", ");
	boost::tokenizer< boost::char_separator<char> > tokens(message, sep);
	std::string messageType, updateType, value;
	int i = 0;
	for (	boost::tokenizer < boost::char_separator < char > >::iterator it = tokens.begin();
			it != tokens.end();
			it++, i++
		)
	{
		switch (i)
		{
		case 0:
			messageType = *it;
			break;
		case 1:
			if (messageType == "trigger")
			{
				//next 2 tokens should be integers
				std::string typeString = ((const std::string&)*it);
				int type = atoi( typeString.c_str() );
				VirtualHardwareController::getHardwareController().processTrigger(
						Protocol::Trigger(Protocol::getTriggerType(type))
				);
			}
			break;
		}
	}
	releaseMutex(handleMessageMutex);
}

// Function: ClientThread
//
// Description:
//    This function is called as a thread, and it handles a given
//    client connection.  The parameter passed in is the socket
//    handle returned from an accept() call.  This function reads
//    data from the client.

DWORD WINAPI ClientReceiveThread(LPVOID lpParam)
{
	ClientData	client = *((ClientData*)lpParam);
    SOCKET&		sock = client.socket;
    char        szBuff[BUFFERSIZE];
    int         nReceived;

    while(1)
    {
        // Perform a non-blocking recv() call
    	nReceived = recv(sock, szBuff, BUFFERSIZE, 0);
        if (nReceived == 0)        // Graceful close
        {
        	closesocket(sock);
//        	WSACleanup();
        	ClientManager::getInstance().removeClient(client.clientID);
        	return 0;
        }
        else if (nReceived == SOCKET_ERROR)
        {
        	unsigned int errorCode = WSAGetLastError();
            if (errorCode == 10035)
            {
            	Sleep(5);
            	continue;
            }
            else
            {
            	printf("recv() failed: %d\n", errorCode);
            	closesocket(sock);
//            	WSACleanup();
            	ClientManager::getInstance().removeClient(client.clientID);
            	return 1;
            }
        }
        szBuff[nReceived] = '\0';
        //handle message
        std::string message = szBuff;
        handleMessage(message);
    }
    ClientManager::getInstance().removeClient(client.clientID);
    return 0;
}


DWORD WINAPI ClientSendThread(LPVOID lpParam)
{
	ClientData	client = *((ClientData*)lpParam);
    SOCKET&		sock = client.socket;
    int         nSend;

    while(1)
    {
    	ClientMessage nextMessage = MessageQueue::getInstance().popNextMessage(client.clientID);
    	if (!ClientManager::getInstance().hasClient(client.clientID))
    	{ //Client closed connection
    		return 1;
    	}
    	if (!nextMessage.hasMessage)
    	{
    		//No message, sleep for a bit and try again
    		Sleep(5);
    		continue;
    	}
    	nSend = send( sock, nextMessage.message.c_str(), nextMessage.message.length(), 0 );
    	if (nSend == SOCKET_ERROR)
    	{
    		printf("send failed with error: %d\n", WSAGetLastError());
    		closesocket(sock);
//    		WSACleanup();
    		ClientManager::getInstance().removeClient(client.clientID);
    		return 1;
    	}
    }
    ClientManager::getInstance().removeClient(client.clientID);
	return 0;
}

DWORD WINAPI runServer(LPVOID lpParam)
{
	WSADATA wsd;
	SOCKET sListen;
	int iAddrSize;
	HANDLE hThread;
	DWORD dwThreadId;
	struct sockaddr_in local, client;

	if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
	{
		printf("Failed to load Winsock!\n");
		return 1;
	}

	// Create our listening socket
	//
	sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if ((int) sListen == SOCKET_ERROR)
	{
		printf("socket() failed: %d\n", WSAGetLastError());
		return 1;
	}
	// Select the local interface and bind to it
	//
	local.sin_addr.s_addr = htonl(INADDR_ANY);
	local.sin_family = AF_INET;
	local.sin_port = htons(PORT);

	if (bind(sListen, (struct sockaddr *) &local,
			sizeof(local)) == SOCKET_ERROR)
	{
		printf("bind() failed: %d\n", WSAGetLastError());
		return 1;
	}
	if (listen(sListen, SOMAXCONN) != 0)
	{
		//TODO: handle inability to put socket in listening mode here
		printf("Could not put socket in listening mode: %d\n", WSAGetLastError());
	}
	//
	// In a continous loop, wait for incoming clients. Once one
	// is detected, create a thread and pass the handle off to it.
	//
	unsigned int clientID = 0;
	while (1)
	{
		SOCKET sClient;
		iAddrSize = sizeof(client);
		sClient = accept(sListen, (struct sockaddr *) &client, &iAddrSize);
		if (sClient == INVALID_SOCKET)
		{
			printf("accept() failed: %d\n", WSAGetLastError());
			break;
		}

		printf("Accepted client: %s:%d\n", inet_ntoa(client.sin_addr),
				ntohs(client.sin_port));

		//Set socket to blocking mode
		unsigned long nonBlockingMode = 0;
		ioctlsocket(sClient, FIONBIO, &nonBlockingMode);

		//Create clientdata struct
		ClientData* data = new ClientData(sClient, clientID);

		//Add client to ClientManager
		ClientManager::getInstance().addClient(clientID);

		//Create client - receive thread
		hThread = CreateThread(NULL, 0, ClientReceiveThread, (LPVOID) data,
				0, &dwThreadId);
		if (hThread == NULL)
		{
			std::cout << "Could not start client listen thread: CreateThread() failed: " << GetLastError() << std::endl;
			//printf("Could not start client listen thread: CreateThread() failed: %d\n", GetLastError());
			break;
		}
		CloseHandle(hThread);

		//Create client - send thread
		hThread = CreateThread(NULL, 0, ClientSendThread, (LPVOID) data, 0,
				&dwThreadId);
		if (hThread == NULL)
		{
			std::cout << "Could not start client send thread: CreateThread() failed: " << GetLastError() << std::endl;
			//printf("Could not start client send thread: CreateThread() failed: %d\n", GetLastError());
			break;
		}
		CloseHandle(hThread);

		//Send current status to new client
		VirtualHardwareController::getHardwareController().updateClient(clientID++);
	}
	closesocket(sListen);

	WSACleanup();
	return 0;
}

class SimulatorServer
{
public:
	static void startServer()
	{
		//Start server
		DWORD dword;
		HANDLE handle = CreateThread(NULL, 0, runServer, NULL, 0, &dword);
		if (handle == NULL)
		{
			//TODO: handle inability to start server here
			std::cout << "Could not start SimulatorServer: CreateThread() failed: " << GetLastError() << std::endl;
			//printf("Could not start SimulatorServer: CreateThread() failed: %d\n", GetLastError());
		}
		CloseHandle(handle);
	}

	static void sendUpdate(Protocol::UpdateType updateType, int value, unsigned int clientID = -1)
	{
		std::stringstream stringStream;
		stringStream << "update ";
		stringStream << updateType << " ";
		stringStream << value;
		stringStream << "\r\n";
		if ((int) clientID == -1)
			MessageQueue::getInstance().addMessage(stringStream.str());
		else
			MessageQueue::getInstance().addMessage(clientID, stringStream.str());
	}

	static void sendUpdate(Protocol::UpdateType updateType, std::string value, unsigned int clientID = -1)
	{
		std::stringstream stringStream;
		stringStream << "update ";
		stringStream << updateType << " ";
		stringStream << value;
		stringStream << "\r\n";
		if ((int) clientID == -1)
			MessageQueue::getInstance().addMessage(stringStream.str());
		else
			MessageQueue::getInstance().addMessage(clientID, stringStream.str());
	}

	~SimulatorServer()
	{

	}
private:
	SimulatorServer();
};

#endif /* SIMULATORSERVER_HPP_ */
